package com.atguigu.gmall.item.service.impl;

import com.atguigu.gmall.item.service.LockService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
public class LockServiceImpl implements LockService {

    @Autowired
    StringRedisTemplate redisTemplate;

    // 在redis的lock位占1
    @Override
    public String lock() {
        String uuid = UUID.randomUUID().toString();

        /**
         *   问题1：占锁操作和释放锁操作之间出现断电问题，导致死锁。解决方案：占锁的时候，给他设置过期时间
         *   问题2：占锁操作和设置过期时间之间出现断电问题，导致死锁。解决方案：占锁操作和设置过期时间的操作设置为原子操作
         *   问题3：业务超长的情况下，锁自动失效，假设存在AB两业务，那么A执行完后会把B的锁给删掉，同时出现了锁不住的情况
         *         - 锁不住的情况：自动续期或者设置长于业务的过期时间
         *         - 删除别人的锁：为了防止删除别人的锁，可以加上UUID
         *   问题4：删除锁的时候，得到锁对应的UUID和判断后的删除操作分为了两步
         *         - 解决：为了保证原子性，引入lua脚本
         */
        while (!redisTemplate.opsForValue().setIfAbsent("lock", uuid, 30, TimeUnit.SECONDS)) {
            try {
                TimeUnit.MICROSECONDS.sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        return uuid;
    }

    @Override
    public void unlock(String uuid) {
        String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
                "then\n" +
                "    return redis.call(\"del\",KEYS[1])\n" +
                "else\n" +
                "    return 0\n" +
                "end";
        Long lock = redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList("lock"), uuid);
        System.out.println("删锁状态："+lock);
    }
}
